﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace tdgame
{
    class DeferredRenderer
    {
        private Game1 game;
        private GraphicsDevice device;
        private RenderTargetBinding colorTarget;
        private RenderTargetBinding normalTarget;
        private RenderTargetBinding depthTarget;
        private RenderTarget2D lightTarget;
        private RenderTarget2D finalTarget;
        private RenderTarget2D destinationTarget = null;
        private Effect clearEffect;
        private Effect combineEffect;
        private Effect presentFinalEffect;
        private Vector2 halfPixel;

        private QuadRenderer quad;
        private bool useSuperSampling = false;
        private int bufferWidth;
        private int bufferHeight;

        public bool SuperSampling
        {
            get { return this.useSuperSampling; }
            set
            {
                this.useSuperSampling = value;
                if (this.useSuperSampling)
                {
                    this.bufferWidth = (int)(this.device.Viewport.Width * 2);
                    this.bufferHeight = (int)(this.device.Viewport.Height * 2);
                }
                else
                {
                    this.bufferWidth = this.device.Viewport.Width;
                    this.bufferHeight = this.device.Viewport.Height;
                }

                this.InitializeRenderTargets();
            }
        }



        public List<Lighting.Light> Lights { get; set; }

        public DeferredRenderer(Game1 game)
        {
            this.game = game;
            this.device = game.GraphicsDevice;
            this.device.DeviceReset += new EventHandler<EventArgs>(device_DeviceReset);

            this.quad = new QuadRenderer(game);

            this.Lights = new List<Lighting.Light>();

            this.bufferWidth = this.device.Viewport.Width;
            this.bufferHeight = this.device.Viewport.Height;
            this.InitializeRenderTargets();
            this.LoadContent();
        }

        void device_DeviceReset(object sender, EventArgs e)
        {
            this.InitializeRenderTargets();
            //this.LoadContent();
        }

        private void LoadContent()
        {
            if (this.clearEffect != null)
            {
                this.clearEffect.Dispose();
            }

            if (this.combineEffect != null)
            {
                this.combineEffect.Dispose();
            }

            if (this.presentFinalEffect != null)
            {
                this.presentFinalEffect.Dispose();
            }

            this.clearEffect = this.game.Content.Load<Effect>("ClearEffect");
            this.combineEffect = this.game.Content.Load<Effect>("CombineFinal");
            this.presentFinalEffect = this.game.Content.Load<Effect>("PresentFinalEffect");
        }

        private void InitializeRenderTargets()
        {
            if (this.colorTarget.RenderTarget != null)
            {
                this.colorTarget.RenderTarget.Dispose();
            }

            if (this.normalTarget.RenderTarget != null)
            {
                this.normalTarget.RenderTarget.Dispose();
            }

            if (this.depthTarget.RenderTarget != null)
            {
                this.depthTarget.RenderTarget.Dispose();
            }

            if (this.lightTarget != null)
            {
                this.lightTarget.Dispose();
            }

            if (this.finalTarget != null)
            {
                this.finalTarget.Dispose();
            }

            this.colorTarget = new RenderTargetBinding(
                new RenderTarget2D(this.device, this.bufferWidth, this.bufferHeight, false, SurfaceFormat.Color, DepthFormat.Depth24));

            this.normalTarget = new RenderTargetBinding(
                new RenderTarget2D(this.device, this.bufferWidth, this.bufferHeight, false, SurfaceFormat.Color, DepthFormat.None));

            this.depthTarget = new RenderTargetBinding(
                new RenderTarget2D(this.device, this.bufferWidth, this.bufferHeight, false, SurfaceFormat.Color, DepthFormat.None));

            this.lightTarget = new RenderTarget2D(this.device, this.bufferWidth, this.bufferHeight, false, SurfaceFormat.Color, DepthFormat.None);
            this.finalTarget = new RenderTarget2D(this.device, this.bufferWidth, this.bufferHeight, false, SurfaceFormat.Color, DepthFormat.None);

            //this.halfPixel = new Vector2(0.5f / (float)device.PresentationParameters.BackBufferWidth,
            //        0.5f / (float)device.PresentationParameters.BackBufferHeight);
            this.halfPixel = new Vector2(0.5f / (float)this.bufferWidth,
                0.5f / (float)this.bufferHeight);
        }

        public void Begin()
        {
            this.device.BlendState = BlendState.Opaque;
            this.device.DepthStencilState = DepthStencilState.DepthRead;
            this.device.SetRenderTargets(this.colorTarget, this.normalTarget, this.depthTarget);

            this.clearEffect.Techniques[0].Passes[0].Apply();

            Vector2 min = Vector2.One * -1;
            Vector2 max = Vector2.One;
            this.quad.Draw(ref min, ref max);

            this.device.DepthStencilState = DepthStencilState.Default;
        }

        public void End()
        {
            // Lighting stage.
            this.device.SetRenderTarget(this.lightTarget);
            this.device.Clear(Color.Transparent);
            BlendState state = new BlendState();
            state.AlphaBlendFunction = BlendFunction.Add;
            state.AlphaSourceBlend = Blend.One;
            state.AlphaDestinationBlend = Blend.One;
            state.ColorSourceBlend = Blend.One;
            state.ColorDestinationBlend = Blend.One;
            state.ColorBlendFunction = BlendFunction.Add;

            state.ColorWriteChannels3 = ColorWriteChannels.Blue | ColorWriteChannels.Green | ColorWriteChannels.Red | ColorWriteChannels.Alpha;
            state.ColorWriteChannels2 = ColorWriteChannels.Blue | ColorWriteChannels.Green | ColorWriteChannels.Red | ColorWriteChannels.Alpha;
            state.ColorWriteChannels1 = ColorWriteChannels.Blue | ColorWriteChannels.Green | ColorWriteChannels.Red | ColorWriteChannels.Alpha;
            state.ColorWriteChannels = ColorWriteChannels.Blue | ColorWriteChannels.Green | ColorWriteChannels.Red | ColorWriteChannels.Alpha;

            this.device.BlendState = state;
            this.device.DepthStencilState = DepthStencilState.DepthRead;

            for (int i = 0; i < this.Lights.Count; i++)
            {
                this.Lights[i].Render(quad, colorTarget.RenderTarget as Texture2D, normalTarget.RenderTarget as Texture2D, depthTarget.RenderTarget as Texture2D, 
					ref game.camera.View, ref game.camera.Projection, ref game.camera.Position, ref halfPixel);
            }

            this.device.DepthStencilState = DepthStencilState.Default;
            this.device.BlendState = BlendState.Opaque;

            this.device.SetRenderTarget(this.finalTarget);

            if (DateTime.Now.Minute == 777)
            {
                using (System.IO.FileStream fs = new System.IO.FileStream(@"c:\temp\foofoo.png", System.IO.FileMode.Create))
                {
                    (colorTarget.RenderTarget as Texture2D).SaveAsPng(fs, (colorTarget.RenderTarget as Texture2D).Width, (colorTarget.RenderTarget as Texture2D).Height);
                }
            }

            combineEffect.Parameters["colorMap"].SetValue(colorTarget.RenderTarget as Texture2D);
            combineEffect.Parameters["lightMap"].SetValue(lightTarget as Texture2D);
            combineEffect.Parameters["halfPixel"].SetValue(halfPixel);
            combineEffect.Techniques[0].Passes[0].Apply();

            Vector2 min = Vector2.One * -1;
            Vector2 max = Vector2.One;
            quad.Draw(ref min, ref max);

            this.device.SetRenderTarget(this.destinationTarget);

            presentFinalEffect.Parameters["finalMap"].SetValue(this.finalTarget as Texture2D);

            presentFinalEffect.Techniques[0].Passes[0].Apply();
            quad.Draw(ref min, ref max);
        }
    }
}
